﻿using System;
using System.Globalization;
using System.Collections.Generic;

namespace Sencha.Serialization
{
	public abstract class JsonReaderBase : IJsonReader
	{
		private static long s_MinDateTicks = new DateTime(0x7b2, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
		protected const int DefaultBufferSize = 1024;

		private sealed class Buffer : System.Collections.Generic.IList<char>
		{
			public const float ShiftThreshold = 0.5f; // postition over 50% of buffer
			public const float GrowThreshold = 0.9f; // when 90% of space is consumed

			private JsonReaderBase m_reader;
			private char[] m_buffer;
			private int m_offset;
			private int? m_lazyFixation;
			private int m_end;
			private int m_initialSize;
			private bool m_isLast;
			private int m_lineNumber;
			private long m_lineStartIndex;
			private long m_charsReaded;
			public bool m_dontCountLines;

			public int Capacity
			{
				get { return m_buffer.Length; }
				set
				{
					if (value <= 0)
						throw new ArgumentOutOfRangeException("value");

					var newBuffer = new char[value];
					BlockCopy(m_buffer, 0, newBuffer, 0, Math.Min(newBuffer.Length, m_buffer.Length));
					m_buffer = newBuffer;
				}
			}
			public int Offset { get { return m_offset; } }
			public long CharactersReaded { get { return m_charsReaded + m_offset; } }
			public int LineNumber { get { return m_lineNumber + 1; } }
			public int ColumnNumber { get { return (int)(this.CharactersReaded - m_lineStartIndex + 1); } }
			public char this[int index]
			{
				get
				{
					if (index < 0)
						throw new ArgumentOutOfRangeException("index");

					if (this.IsBeyondOfStream(index))
						return (char)0;

					return m_buffer[m_offset + index];
				}
			}

			public Buffer(int size, JsonReaderBase reader)
			{
				if (size <= 0)
					throw new ArgumentOutOfRangeException("size");
				if (reader == null)
					throw new ArgumentNullOrEmptyException("reader");
				System.Diagnostics.Contracts.Contract.EndContractBlock();

				if (size < 10)
					size = 10;

				m_reader = reader;
				m_buffer = new char[size];
				m_initialSize = size;
				m_end = 0;
				m_offset = 0;
			}

			public void FixateNow()
			{
				if (m_lazyFixation != null)
				{
					this.Fixate(m_lazyFixation.Value);
					m_lazyFixation = null;
				}
			}
			public void Fixate(int atIndex)
			{
				if (atIndex < 0)
					throw new ArgumentOutOfRangeException("position");
				System.Diagnostics.Contracts.Contract.EndContractBlock();

				if (!m_dontCountLines)
				{
					for (var i = 0; i < atIndex; i++)
					{
						if (this[i] == '\n')
						{
							m_lineNumber++;
							m_lineStartIndex = this.CharactersReaded + i;
						}
					}
				}

				// ensure that fixation point in loaded range
				IsBeyondOfStream(atIndex);

				m_offset += atIndex;

				// when we are at second half of buffer - we need to shift back
				if ((m_offset / (float)m_buffer.Length) > ShiftThreshold)
					this.ShiftToZero();
			}
			public void FixateLater(int atIndex)
			{
				if (atIndex < 0)
					throw new ArgumentOutOfRangeException("position");
				System.Diagnostics.Contracts.Contract.EndContractBlock();

				if (m_lazyFixation != null)
					m_lazyFixation += atIndex;
				else
					m_lazyFixation = atIndex;
			}

			public bool IsBeyondOfStream(int index)
			{
				if (!m_isLast && m_offset + index >= m_end)
					this.ReadNextBlock();

				if (m_isLast && m_offset + index >= m_end)
					return true;

				return false;
			}
			public char[] GetChars()
			{
				return m_buffer;
			}
			public void Reset()
			{
				this.FixateNow();
				this.ShiftToZero();
				m_charsReaded = 0;
				m_lineNumber = 0;
				m_lineStartIndex = 0;
			}

			private void ReadNextBlock()
			{
				// when we are at second half of buffer - we need to shift back
				if ((m_offset / (float)m_buffer.Length) > ShiftThreshold)
					this.ShiftToZero();

				// check for free space
				var free = m_buffer.Length - m_end;
				if ((free / (float)m_initialSize) < GrowThreshold)
					this.Capacity += m_initialSize;

				var newEnd = m_reader.FillBuffer(m_buffer, m_end);
				m_isLast = newEnd == m_end;
				m_end = newEnd;
			}

			private void ShiftToZero()
			{
				m_charsReaded += m_offset;

				var block = Math.Min(m_offset, m_end - m_offset);
				var start = m_offset;
				var lastBlock = 0;
				while (start < m_end)
				{
					BlockCopy(m_buffer, start, m_buffer, lastBlock, Math.Min(block, m_end - start));
					lastBlock += block;
					start += block;
				}
				m_end = m_end - m_offset;
				m_offset = 0;
#if DEBUG
				if (m_end < m_buffer.Length) // zero unused space(just for debug)
					Array.Clear(m_buffer, m_end, m_buffer.Length - m_end);
#endif
			}

			private static void BlockCopy(char[] from, int fromIdx, char[] to, int toIdx, int len)
			{
				const int charSize = sizeof(char);

				System.Buffer.BlockCopy(from, fromIdx * charSize, to, toIdx * charSize, len * charSize);
			}

			public override string ToString()
			{
				return new string(m_buffer, m_offset, m_end - m_offset);
			}

			#region IList<char> Members

			int System.Collections.Generic.IList<char>.IndexOf(char item)
			{
				throw new NotSupportedException();
			}

			void System.Collections.Generic.IList<char>.Insert(int index, char item)
			{
				throw new NotSupportedException();
			}

			void System.Collections.Generic.IList<char>.RemoveAt(int index)
			{
				throw new NotSupportedException();
			}

			char System.Collections.Generic.IList<char>.this[int index]
			{
				get
				{
					return this[index];
				}
				set
				{
					throw new NotImplementedException();
				}
			}

			#endregion

			#region ICollection<char> Members

			void System.Collections.Generic.ICollection<char>.Add(char item)
			{
				throw new NotSupportedException();
			}

			void System.Collections.Generic.ICollection<char>.Clear()
			{
				throw new NotSupportedException();
			}

			bool System.Collections.Generic.ICollection<char>.Contains(char item)
			{
				throw new NotSupportedException();
			}

			void System.Collections.Generic.ICollection<char>.CopyTo(char[] array, int arrayIndex)
			{
				throw new NotSupportedException();
			}

			int System.Collections.Generic.ICollection<char>.Count
			{
				get { return m_buffer.Length; }
			}

			bool System.Collections.Generic.ICollection<char>.IsReadOnly
			{
				get { return true; }
			}

			bool System.Collections.Generic.ICollection<char>.Remove(char item)
			{
				throw new NotSupportedException();
			}

			#endregion

			#region IEnumerable<char> Members

			System.Collections.Generic.IEnumerator<char> System.Collections.Generic.IEnumerable<char>.GetEnumerator()
			{
				return (this.m_buffer as System.Collections.Generic.IList<char>).GetEnumerator();
			}

			#endregion

			#region IEnumerable Members

			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
			{
				return this.m_buffer.GetEnumerator();
			}

			#endregion
		}
		private sealed class LazyValueInfo : IValueInfo
		{
			enum Kind : byte { Explicit = 0, QuotedString, String };

			private JsonReaderBase m_reader;
			private int m_jsonStart;
			private int m_jsonLen;
			private bool m_hasValue;
			private object m_value;
			private Kind m_valueKind;
			private int m_lineNum;
			private int m_colNum;
			private Dictionary<Type, IStrongBox> m_valueContainers;

			public bool HasValue { get { return m_hasValue; } }
			public object Raw
			{
				get
				{
					// eval lazy value
					if (m_valueKind == Kind.String)
						this.Raw = new string(m_reader.m_buffer.GetChars(), m_reader.m_buffer.Offset + m_jsonStart, m_jsonLen);
					else if (m_valueKind == Kind.QuotedString)
						this.Raw = JsonUtils.UnescapeBuffer(m_reader.m_buffer.GetChars(), m_reader.m_buffer.Offset + m_jsonStart, m_jsonLen);

					if (m_value is IStrongBox)
						return (m_value as IStrongBox).AsObject;
					else
						return m_value;
				}
				set
				{
					m_valueKind = Kind.Explicit;
					m_value = value;
					m_hasValue = true;
				}
			}
			public Type RawType
			{
				get
				{
					if (m_valueKind != Kind.Explicit)
					{
						switch (m_reader.m_token)
						{
							case JsonToken.Number:

								var buffer = m_reader.m_buffer.GetChars();
								var start = m_jsonStart + m_reader.m_buffer.Offset;
								var length = m_jsonLen;

								//if (m_valueKind == Kind.QuotedString)
								//{
								//    start -= 1;
								//    length += 2;
								//}
#if STRICT
								if (Array.IndexOf(buffer, ".", start, length) != -1)
									return typeof(double);
								else
									return typeof(Int64);
#else
								var numberFormat = (System.Globalization.NumberFormatInfo)m_reader.Context.Format.GetFormat(typeof(System.Globalization.NumberFormatInfo));
								if (Array.IndexOf(buffer, numberFormat.NumberDecimalSeparator, start, length) != -1)
									return typeof(double);
								else
									return typeof(Int64);
#endif
							case JsonToken.StringLiteral: return typeof(string);
							case JsonToken.DateTime: return typeof(DateTime);
							case JsonToken.Boolean: return typeof(Boolean);
							default: return null;
						}
					}


					if (m_value is IStrongBox)
						return (m_value as IStrongBox).ValueType;
					else if (m_value != null)
						return m_value.GetType();
					else
						return null;
				}
			}
			public bool AsBoolean { get { return this.AsType<bool>(); } }
			public byte AsByte { get { if (m_value is IStrongBox) return (m_value as IStrongBox).AsByte; else return this.AsType<Byte>(); } }
			public short AsInt16 { get { if (m_value is IStrongBox) return (m_value as IStrongBox).AsInt16; else return this.AsType<Int16>(); } }
			public int AsInt32 { get { if (m_value is IStrongBox) return (m_value as IStrongBox).AsInt32; else return this.AsType<Int32>(); } }
			public long AsInt64 { get { if (m_value is IStrongBox) return (m_value as IStrongBox).AsInt64; else return this.AsType<Int64>(); } }
			public sbyte AsSByte { get { if (m_value is IStrongBox) return (m_value as IStrongBox).AsSByte; else return this.AsType<SByte>(); } }
			public ushort AsUInt16 { get { if (m_value is IStrongBox) return (m_value as IStrongBox).AsUInt16; else return this.AsType<UInt16>(); } }
			public uint AsUInt32 { get { if (m_value is IStrongBox) return (m_value as IStrongBox).AsUInt32; else return this.AsType<UInt32>(); } }
			public ulong AsUInt64 { get { if (m_value is IStrongBox) return (m_value as IStrongBox).AsUInt64; else return this.AsType<UInt64>(); } }
			public float AsSingle { get { if (m_value is IStrongBox) return (m_value as IStrongBox).AsSingle; else return this.AsType<Single>(); } }
			public double AsDouble { get { if (m_value is IStrongBox) return (m_value as IStrongBox).AsDouble; else return this.AsType<Double>(); } }
			public decimal AsDecimal { get { if (m_value is IStrongBox) return (m_value as IStrongBox).AsDecimal; else return this.AsType<Decimal>(); } }
			public DateTime AsDateTime { get { return this.AsType<DateTime>(); } }
			public string AsString
			{
				get
				{
					var raw = this.Raw;
					if (raw is string)
						return (string)raw;
					else
						return Convert.ToString(raw, m_reader.Context.Format);
				}
			}
			public string AsJson
			{
				get
				{
					var buffer = m_reader.m_buffer.GetChars();
					var start = m_jsonStart + m_reader.m_buffer.Offset;
					var length = m_jsonLen;

					return new string(buffer, start, length);
				}
			}

			public int LineNumber { get { return m_lineNum; } }
			public int ColumnNumber { get { return m_colNum; } }

			public LazyValueInfo(JsonReaderBase reader)
			{
				if (reader == null)
					throw new ArgumentNullException("reader");
				System.Diagnostics.Contracts.Contract.EndContractBlock();

				m_valueContainers = new Dictionary<Type, IStrongBox>(32);
				m_reader = reader;
			}

			public T AsType<T>()
			{
				var result = default(T);
				if (m_valueKind == Kind.Explicit)
				{
					var value = m_value;
					if (value is IStrongBox)
						result = (value as IStrongBox).Load<T>();
					else if (value is T)
						result = (T)value;
					else if (result is DateTime)
					{
                        var date = default(DateTime);
                        if (m_reader.Token == JsonToken.Number)
                            date = new DateTime(long.Parse((string)this.Raw), DateTimeKind.Utc);
                        else
                            date = DateTime.ParseExact((string)this.Raw, m_reader.Context.DateTimeFormats, m_reader.Context.Format, DateTimeStyles.AssumeUniversal);
#if NO_TYPE_REFS
						result = (T)(object)@date;
#else
						result = __refvalue( __makeref(date),T);
#endif
                        
					}
					else if (value is string)
						result = TypeConvert.Convert<string,T>((string)value, m_reader.Context.Format);
					else
						result = (T)value;
				}
				else
				{
					var buffer = m_reader.m_buffer.GetChars();
					var start = m_jsonStart + m_reader.m_buffer.Offset;
					var length = m_jsonLen;

					result = JsonUtils.ConvertFromBuffer<T>(buffer, start, length, m_reader.Context.Format);

#if NO_TYPE_REFS
					this.SetTypedValue(result);
#else
					this.SetTypedValue(__makeref(result));
#endif

				}

				return result;
			}

#if NO_TYPE_REFS
			public void SetTypedValue<T>(T value)
			{
				var valueType = typeof(T);
				if (valueType.IsValueType)
				{
					var container = default(IStrongBox);
					if (!m_valueContainers.TryGetValue(valueType, out container))
					{
						container = (IStrongBox)Activator.CreateInstance(typeof(StrongBox<>).MakeGenericType(valueType));
						m_valueContainers[valueType] = container;
					}
					container.Store(value);
					this.Raw = container;
				}
				else
					this.Raw = (object)value;
			}
#else
			public void SetTypedValue(TypedReference value)
			{
				var valueType = __reftype(value);
				if (valueType.IsValueType)
				{
					var container = default(IStrongBox);
					if (!m_valueContainers.TryGetValue(valueType, out container))
					{
						container = (IStrongBox)Activator.CreateInstance(typeof(StrongBox<>).MakeGenericType(valueType));
						m_valueContainers[valueType] = container;
					}
					container.Store(value);
					this.Raw = container;
				}
				else
					this.Raw = TypedReference.ToObject(value);
			}
#endif
			public void ClearValue()
			{
				m_value = null;
				m_hasValue = false;
			}

			public void CopyJsonTo(System.Text.StringBuilder stringBuilder)
			{
				if (stringBuilder == null)
					throw new ArgumentNullException("stringBuilder");
				System.Diagnostics.Contracts.Contract.EndContractBlock();

				var buffer = m_reader.m_buffer.GetChars();
				var start = m_jsonStart + m_reader.m_buffer.Offset;
				var length = m_jsonLen;

				if (m_valueKind == Kind.QuotedString)
				{
					start -= 1;
					length += 2;
				}

				stringBuilder.Append(buffer, start, length);
			}
			public void CopyJsonTo(System.IO.TextWriter writer)
			{
				if (writer == null)
					throw new ArgumentNullException("writer");
				System.Diagnostics.Contracts.Contract.EndContractBlock();

				var buffer = m_reader.m_buffer.GetChars();
				var start = m_jsonStart + m_reader.m_buffer.Offset;
				var length = m_jsonLen;

				if (m_valueKind == Kind.QuotedString)
				{
					start -= 1;
					length += 2;
				}

				writer.Write(buffer, start, length);
			}
			public void CopyJsonTo(System.IO.Stream stream)
			{
				if (stream == null)
					throw new ArgumentNullException("stream");
				System.Diagnostics.Contracts.Contract.EndContractBlock();

				var buffer = m_reader.m_buffer.GetChars();
				var start = m_jsonStart + m_reader.m_buffer.Offset;
				var length = m_jsonLen;
				var writer = new System.IO.BinaryWriter(stream, m_reader.Context.Encoding);

				if (m_valueKind == Kind.QuotedString)
				{
					start -= 1;
					length += 2;
				}

				writer.Write(buffer, start, length);
				writer.Flush();
			}
			public void CopyJsonTo(IJsonWriter writer)
			{
				if (writer == null)
					throw new ArgumentNullException("writer");
				System.Diagnostics.Contracts.Contract.EndContractBlock();

				var buffer = m_reader.m_buffer.GetChars();
				var start = m_jsonStart + m_reader.m_buffer.Offset;
				var length = m_jsonLen;

				if (m_valueKind == Kind.QuotedString)
				{
					start -= 1;
					length += 2;
				}

				writer.WriteJson(buffer, start, length);
			}

			public void SetBufferBounds(int start, int len)
			{
				if (start < 0)
					throw new ArgumentOutOfRangeException("start");
				if (len < 0)
					throw new ArgumentOutOfRangeException("len");
				System.Diagnostics.Contracts.Contract.EndContractBlock();

				m_lineNum = m_reader.m_buffer.LineNumber;
				m_colNum = m_reader.m_buffer.ColumnNumber + start;
				m_jsonStart = start;
				m_jsonLen = len;
			}
			public void SetAsLazyString(bool quoted)
			{
				m_valueKind = quoted ? Kind.QuotedString : Kind.String;
				m_hasValue = true;
			}
		}

		private const char InsignificantTab = '\t';
		private const char InsignificantSpace = ' ';
		private const char InsignificantNewline = '\n';
		private const char InsignificantReturn = '\r';
		private const char InsignificantNameSeparator = ':';
		private const char InsignificantValueSeparator = ',';
		private const char SignificantBeginArray = '[';
		private const char SignificantEndArray = ']';
		private const char SignificantBeginObject = '{';
		private const char SignificantEndObject = '}';
		private const char SignificantQuote = '\"';
		private const char SignificantQuoteAlt = '\'';

		private LazyValueInfo m_value;
		private JsonToken m_token;
		private Buffer m_buffer;

		protected JsonReaderBase(ISerializationContext context, int bufferSize = DefaultBufferSize)
		{
			if (context == null)
				throw new ArgumentNullException("context");
			if (bufferSize <= 0)
				throw new IndexOutOfRangeException("bufferSize");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			this.Context = context;
			m_value = new LazyValueInfo(this);
			m_buffer = new Buffer(bufferSize, this);
		}

		#region IJsonReader Members

		public ISerializationContext Context { get; private set; }

		public IValueInfo Value
		{
			get
			{
				if (this.Token == JsonToken.None)
					this.NextToken();
				return m_value;
			}
		}
		public JsonToken Token
		{
			get
			{
				if (m_token == JsonToken.None)
					this.NextToken();
				return m_token;
			}
		}
		public object RawValue { get { return this.Value.Raw; } }
		public long CharactersReaded { get { return this.m_buffer.CharactersReaded; } }

		public bool NextToken()
		{
			var start = 0;
			var len = 0;
			var quoted = false;
			var isMember = false;
			var buffer = this.NextLexeme(ref start, ref len, ref quoted, ref isMember);

			if (buffer == null) // end of stream
			{
				m_token = JsonToken.EndOfStream;
				m_value.Raw = null;
				return false;
			}

			m_value.ClearValue();
			m_value.SetBufferBounds(start, len);
			if (len == 1 && !quoted && m_buffer[start] == SignificantBeginArray)
			{
				m_token = JsonToken.BeginArray;
			}
			else if (len == 1 && !quoted && m_buffer[start] == SignificantBeginObject)
			{
				m_token = JsonToken.BeginObject;
			}
			else if (len == 1 && !quoted && m_buffer[start] == SignificantEndArray)
			{
				m_token = JsonToken.EndOfArray;
			}
			else if (len == 1 && !quoted && m_buffer[start] == SignificantEndObject)
			{
				m_token = JsonToken.EndOfObject;
			}
			else if (len == 4 && !quoted && this.LookupAt(buffer, start, len, "null"))
			{
				m_token = JsonToken.Null;
			}
			else if (len == 4 && !quoted && this.LookupAt(buffer, start, len, "true"))
			{
				m_token = JsonToken.Boolean;
				var trueValue = true;
#if NO_TYPE_REFS
				m_value.SetTypedValue(trueValue);
#else
				m_value.SetTypedValue(__makeref(trueValue));
#endif
				m_value.Raw = true;
			}
			else if (len == 5 && !quoted && this.LookupAt(buffer, start, len, "false"))
			{
				m_token = JsonToken.Boolean;
				var falseValue = false;
#if NO_TYPE_REFS
				m_value.SetTypedValue(falseValue);
#else
				m_value.SetTypedValue(__makeref(falseValue));
#endif
			}
			else if (quoted && LookupAt(buffer, start, 6, "/Date(") && LookupAt(buffer, start + len - 2, 2, ")/"))
			{
				long ticks = JsonUtils.StringToInt64(buffer.GetChars(), buffer.Offset + start + 6, len - 8);

				m_token = JsonToken.DateTime;
				var dateTime = new DateTime(ticks * 0x2710L + s_MinDateTicks, DateTimeKind.Utc);
#if NO_TYPE_REFS
				m_value.SetTypedValue(dateTime);
#else
				m_value.SetTypedValue(__makeref(dateTime));
#endif
			}
			else if (!quoted && this.IsNumber(buffer, start, len))
			{
				m_token = JsonToken.Number;
				m_value.SetAsLazyString(false);
			}
			else
			{
				m_token = isMember ? JsonToken.Member : JsonToken.StringLiteral;
				m_value.SetAsLazyString(quoted);
			}

			m_buffer.FixateLater(start + len + (quoted ? 1 : 0));

			return true;
		}
		public bool IsEndOfStream()
		{
			return m_token == JsonToken.EndOfStream;
		}
		public void Reset()
		{
			m_buffer.Reset();
			if (m_token != JsonToken.EndOfStream)
			{
				m_value = new LazyValueInfo(this);
				m_token = JsonToken.None;
			}
		}

		#endregion

		private string Unescape(Buffer buffer, int start, int len)
		{
			if (buffer == null)
				throw new ArgumentNullException("buffer");
			if (start < 0)
				throw new ArgumentOutOfRangeException("start");
			if (len < 0)
				throw new ArgumentOutOfRangeException("len");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (len == 0)
				return string.Empty;

			var sb = new System.Text.StringBuilder(len);
			var unencStart = 0;
			var unencLen = 0;
			for (int i = 0; i < len; i++)
			{

				var ch = buffer[start + i];
				if (ch == '\\')
				{
					var escapeLength = 1;
					// append unencoded chunk
					if (unencLen != 0)
					{
						sb.Append(buffer.GetChars(), buffer.Offset + start + unencStart, unencLen);
						unencLen = 0;
					}

					var escapeChar = buffer[start + i + 1];
					switch (escapeChar)
					{
						case 'n': sb.Append('\n'); break;
						case 'r': sb.Append('\r'); break;
						case 'b': sb.Append('\b'); break;
						case 'f': sb.Append('\f'); break;
						case 't': sb.Append('\t'); break;
						case '\\': sb.Append('\\'); break;
						case '\'': sb.Append('\''); break;
						case '\"': sb.Append('\"'); break;
						// unicode symbol
						case 'u':
							buffer.IsBeyondOfStream(start + i + 2 + 4); // ensures next 4 characters in buffer
							sb.Append((char)JsonUtils.HexStringToUInt32(buffer.GetChars(), buffer.Offset + start + i + 2, 4));
							escapeLength = 5;
							break;
						// latin hex encoded symbol
						case 'x':
							buffer.IsBeyondOfStream(start + i + 2 + 2); // ensures next 2 characters in buffer
							sb.Append((char)JsonUtils.HexStringToUInt32(buffer.GetChars(), buffer.Offset + start + i + 2, 2));
							escapeLength = 3;
							break;
						// latin dec encoded symbol
						case '1':
						case '2':
						case '3':
						case '4':
						case '5':
						case '6':
						case '7':
						case '8':
						case '9':
						case '0':
							buffer.IsBeyondOfStream(start + i + 2 + 3); // ensures next 3 characters in buffer
							sb.Append((char)JsonUtils.StringToUInt32(buffer.GetChars(), buffer.Offset + start + i + 1, 3));
							escapeLength = 3;
							break;
						default:
#if STRICT
							throw new Exceptions.UnknownEscapeSequence("\\" + escapeChar.ToString(), this);
#else
						sb.Append(escapeChar);
						break;
#endif
					}

					// set next chunk start right after this escape
					unencStart = i + escapeLength + 1;
					i += escapeLength;
				}
				else
				{
					unencLen++;
				}
			}

			// append last unencoded chunk
			if (unencLen != 0)
				sb.Append(buffer.GetChars(), buffer.Offset + start + unencStart, unencLen);

			return sb.ToString();
		}
		private bool IsNumber(Buffer buffer, int start, int len)
		{
			if (buffer == null)
				throw new ArgumentNullException("buffer");
			if (start < 0)
				throw new ArgumentOutOfRangeException("start");
			if (len < 0)
				throw new ArgumentOutOfRangeException("len");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			const int IntPart = 0;
			const int FracPart = 1;
			const int ExpPart = 2;
			const char POINT = '.';
			const char EXP = 'E';
			const char PLUS = '+';
			const char MINUS = '-';

			len = start + len;

			var part = IntPart;

			for (int i = start; i < len; i++)
			{
				var ch = buffer[i];

				switch (part)
				{
					case IntPart:
						if (ch == MINUS)
						{
							if (i != start)
								return false;
						}
#if !STRICT
						else if (ch == PLUS)
						{
							if (i != start)
								return false;
						}
#endif
						else if (ch == POINT)
						{
							if (i == start)
								return false; // decimal point as first character
							else
								part = FracPart;
						}
						else if (Char.ToUpper(ch) == EXP)
						{
							if (i == start)
								return false; // exp at first character
							else
								part = ExpPart;
						}
						else if (!Char.IsDigit(ch))
							return false; // non digit character in int part
						break;
					case FracPart:
						if (Char.ToUpper(ch) == EXP)
						{
							if (i == start)
								return false; // exp at first character
							else
								part = ExpPart;
						}
						else if (!Char.IsDigit(ch))
							return false; // non digit character in frac part
						break;
					case ExpPart:
						if ((ch == PLUS || ch == MINUS))
						{
							if (Char.ToUpper(buffer[i - 1]) != EXP)
								return false; // sign not at start of exp part
						}
						else if (!Char.IsDigit(ch))
							return false; // non digit character in exp part
						break;
				}
			}
			return true;
		}
		private bool IsInsignificantWhitespace(char symbol)
		{
			return symbol == InsignificantNewline || symbol == InsignificantReturn || symbol == InsignificantSpace || symbol == InsignificantTab;
		}
		private bool IsInsignificant(char symbol)
		{
			return symbol == InsignificantNewline || symbol == InsignificantReturn || symbol == InsignificantSpace || symbol == InsignificantTab || symbol == InsignificantNameSeparator || symbol == InsignificantValueSeparator;
		}
		private bool IsLiteralTerminator(char ch, bool quoted, char quoteCh, bool escaped, bool eos)
		{
			if (!escaped && quoted && ch == quoteCh)
				return true;
			else if (quoted && (ch == InsignificantNewline || ch == InsignificantReturn))
				throw new Exceptions.UnterminatedStringLiteral(this);
			else if (eos)
			{
				if (quoted)
					throw new Exceptions.UnexpectedEndOfStream(this);
				else
					return true;
			}
			else if (!quoted && (ch == SignificantBeginArray || ch == SignificantBeginObject || ch == SignificantEndArray || ch == SignificantEndObject || ch == InsignificantValueSeparator || ch == InsignificantNameSeparator))
				return true;
			else if (!quoted && IsInsignificantWhitespace(ch))
				return true;

			return false;
		}
		private bool LookupAt(Buffer buffer, int start, int len, string matchString)
		{
			for (var i = 0; i < len; i++)
			{
				if (buffer[start + i] != matchString[i])
					return false;
			}
			return true;
		}
		private bool LookupAtSkipWhitespace(Buffer buffer, int start, int len, string matchString)
		{
			while (IsInsignificantWhitespace(buffer[start])) start++;

			for (var i = 0; i < len; i++)
			{
				if (buffer[start + i] != matchString[i])
					return false;
			}
			return true;
		}

		/// <summary>
		/// Get next lexeme from current buffer
		/// </summary>
		/// <param name="start">return position of returned lexeme in buffer</param>
		/// <param name="len">return size of returned lexeme</param>
		/// <param name="quoted">return true when string literal was quoted</param>
		/// <returns>Null in case of "end of stream", or character buffer with result</returns>
		private Buffer NextLexeme(ref int start, ref int len, ref bool quoted, ref bool isMember)
		{
			// apply 'lazy' fixation
			m_buffer.FixateNow();

			if (m_buffer.IsBeyondOfStream(0))
				return null;

			var position = 0;
			var ch = m_buffer[position];

			// skip insignificant characters
			while (!m_buffer.IsBeyondOfStream(position) && this.IsInsignificant(m_buffer[position])) position++;

			// we reached end of stream
			if (m_buffer.IsBeyondOfStream(position))
				return null;


			// tell buffer that significant characters starts here
			// this prevents buffer overgrow
			m_buffer.Fixate(position);
			position = 0;
			var literalStart = position;
			//

			ch = m_buffer[position];
			//
			// check for quote character
			var quoteCh = '\0';
			if (ch == SignificantQuote)
			{
				quoteCh = ch;
				quoted = true;
				position++;
				literalStart++;
			}
#if !STRICT
			else if (ch == SignificantQuoteAlt)
			{
				quoteCh = ch;
				quoted = true;
				position++;
				literalStart++;
			}
#endif
			var escaped = false; // is character is escaped
			var eos = false; // is end of stream
			do
			{
				eos = m_buffer.IsBeyondOfStream(position);
				escaped = ch == '\\';
				ch = m_buffer[position];
				position++;
			} while (!this.IsLiteralTerminator(ch, quoted, quoteCh, escaped, eos));

			var literalEnd = position - 1; // minus terminator character

			start = literalStart;
			len = literalEnd - literalStart;

			// special case - self terminated lexeme
			if (literalStart == literalEnd && !quoted)
				len = 1;

			isMember = this.LookupAtSkipWhitespace(m_buffer, literalEnd + (quoted ? 1 : 0), 1, ":");

			return m_buffer;
		}

		/// <summary>
		/// Fills buffer with new characters, staring from <paramref name="index"/>
		/// </summary>
		/// <param name="buffer">Character buffer to fill</param>
		/// <param name="index">index from which to start</param>
		/// <returns>new buffer size</returns>
		protected abstract int FillBuffer(char[] buffer, int index);
	}
}
